'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with self work for additional information
 * regarding copyright ownership.  The ASF licenses self file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use self file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''


#import java.util.*;

#import org.apache.jena.rdf.model.*;
#import org.apache.jena.vocabulary.*;

'''
 * The ModelExpansion code expands a model <code>M</code> against a
 * schema <code>S</code>, returning a model which contains
 *
 * <ul>
 * <li>the statements of M
 * <li>any statements (A rdfs:subClassOf B) from S where neither A nor B
 * is a bnode.
 * <li>statements (A rdf:type T) if M contains (A P any) and
 * S contains (P rdfs:domain T).
 * <li>statements (A rdf:type T) if M contains (any P A) and
 * S contains (P rdfs:range T).
 * <li>statements (A rdf:type T) if (A rdf:type U) and (U rdfs:subClassOf T).
 * </ul>
 * <p>
 * This is sufficient to allow the subjects in <code>M</code> which have
 * properties from <code>S</code> to have enough type information for
 * AssemblerGroup dispatch.
'''
ModelExpansion
    '''
     * Answer a model which is the aggregation of
     * <ul>
     * <li>the statements of <code>model</code>
     * <li>the non-bnode subclass statements of <code>schema</code>
     * <li>the subclass closure of those statements
     * <li>the rdf:type statements implied by the rdfs:domain statements
     * of <code>schema</code> and the <code>model</code>
     * statements using that statements property
     * <li>similarly for rdfs:range
     * <li>the rdf:type statements implied by the subclass closure
     * </ul>
    '''
    # param Model model, Model schema
    # result static Model
    def withSchema(self, model, schema):
        Model result = ModelFactory.createDefaultModel().add(model)
        addSubclassesFrom(result, schema)
        addSubClassClosure(result)
        addDomainTypes(result, schema)
        addRangeTypes(result, schema)
        addIntersections(result, schema)
        addSupertypes(result)
        return result
    

    private static final Property ANY = null

    # param Model result, Model schema
    # result static void
    def addSubclassesFrom(self, result, schema):
        for (StmtIterator it = schema.listStatements(ANY, RDFS.subClassOf, ANY) it.hasNext() ):
            Statement s = it.nextStatement()
            if (s.getSubject().isURIResource() and s.getObject().isURIResource()) result.add(s):
        
    

    '''
     * Do (limited) subclass closure on <code>m</code>.
     * <p>
     * Those classes in <code>m</code> that appear in <code>subClassOf</code>
     * statements are given as explicit superclasses all their indirect superclasses.
    '''
    # param Model m
    # result static void
    def addSubClassClosure(self, m):
        Set<Resource> roots = selectRootClasses(m, findClassesBySubClassOf(m))
        for (Resource root : roots):
            addSuperClasses(m, root)
        
    

    '''
     * To each subclass X of <code>type</code> add as superclass all the
     * classes between X and <code>type</code>.
    '''
    # param Model m, Resource type
    # result static void
    def addSuperClasses(self, m, type):
        addSuperClasses(m, LinkedSeq(type))
    

    '''
     * To each subclass X of <code>parents.item</code> add as superclass
     * all the classes between X and that item and all the items in the
     * rest of <code>parents</code>.
    '''
    # param Model m, LinkedSeq parents
    # result static void
    def addSuperClasses(self, m, parents):
        Model toAdd = ModelFactory.createDefaultModel()
        addSuperClasses(m, parents, toAdd)
        m.add(toAdd)
    

    '''
     * Add to <code>toAdd</code> all the superclass statements needed
     * to note that any indirect subclass of <code>X = parents.item</code> has
     * as superclass all the classes between it and X and all the remaining
     * elements of <code>parents</code>.
    '''
    # param Model m, LinkedSeq parents, Model toAdd
    # result static void
    def addSuperClasses(self, m, parents, toAdd):
        Resource type = parents.item
        for (StmtIterator it = m.listStatements(null, RDFS.subClassOf, type) it.hasNext() ):
            Resource t = it.nextStatement().getSubject()
            for (LinkedSeq scan = parents.rest scan is not None scan = scan.rest)
                toAdd.add(t, RDFS.subClassOf, scan.item)
            addSuperClasses(m, parents.push(t), toAdd)
        
    

    '''
     * Answer the subset of <code>classes</code> which have no
     * superclass in <code>m</code>.
    '''
    # param Model m, Set<RDFNode> classes
    # result static Set<Resource>
    def selectRootClasses(self, m, classes):
        Set<Resource> roots = HashSet<>()
        for (RDFNode aClass : classes):
            Resource type = (Resource) aClass
            if (!m.contains(type, RDFS.subClassOf, (RDFNode) null)):
                roots.add(type)
            
        
        return roots
    

    '''
     * Answer the set of all classes which appear in <code>m</code> as the
     * subject or object of a <code>rdfs:subClassOf</code> statement.
    '''
    # param Model m
    # result static Set<RDFNode>
    def findClassesBySubClassOf(self, m):
        Set<RDFNode> classes = HashSet<>()
        StmtIterator it = m.listStatements(null, RDFS.subClassOf, (RDFNode) null)
        while (it.hasNext()) addClasses(classes, it.nextStatement())
        return classes
    

    '''
     * Add to <code>classes</code> the subject and object of the statement
     * <code>xSubClassOfY</code>.
    '''
    # param Set<RDFNode> classes, Statement xSubClassOfY
    # result static void
    def addClasses(self, classes, xSubClassOfY):
        classes.add(xSubClassOfY.getSubject())
        classes.add(xSubClassOfY.getObject())
    

    # param Model result, Model schema
    # result static void
    def addDomainTypes(self, result, schema):
        for (StmtIterator it = schema.listStatements(ANY, RDFS.domain, ANY) it.hasNext() ):
            Statement s = it.nextStatement()
            Property property = s.getSubject().as(Property.class)
            RDFNode type = s.getObject()
            for (StmtIterator x = result.listStatements(ANY, property, ANY) x.hasNext() ):
                Statement t = x.nextStatement()
                result.add(t.getSubject(), RDF.type, type)
            
        
    

    # param Model result, Model schema
    # result static void
    def addRangeTypes(self, result, schema):
        Model toAdd = ModelFactory.createDefaultModel()
        for (StmtIterator it = schema.listStatements(ANY, RDFS.range, ANY) it.hasNext() ):
            Statement s = it.nextStatement()
            RDFNode type = s.getObject()
            Property property = s.getSubject().as(Property.class)
            for (StmtIterator x = result.listStatements(ANY, property, ANY) x.hasNext() ):
                RDFNode ob = x.nextStatement().getObject()
                if (ob.isResource()) toAdd.add((Resource) ob, RDF.type, type):
            
        
        result.add(toAdd)
    

    # param Model result
    # result static void
    def addSupertypes(self, result):
        Model temp = ModelFactory.createDefaultModel()
        for (StmtIterator it = result.listStatements(ANY, RDF.type, ANY) it.hasNext() ):
            Statement s = it.nextStatement()
            Resource c = AssemblerHelp.getResource(s)
            for (StmtIterator subclasses = result.listStatements(c, RDFS.subClassOf, ANY) subclasses.hasNext() ):
                RDFNode type = subclasses.nextStatement().getObject()
                temp.add(s.getSubject(), RDF.type, type)
            
        
        result.add(temp)
    

    # param Model result, Model schema
    # result static void
    def addIntersections(self, result, schema):
        StmtIterator it = schema.listStatements(ANY, OWL.intersectionOf, ANY)
        while (it.hasNext()) addIntersections(result, schema, it.nextStatement())
    

    # param Model result, Model schema, Statement s
    # result static void
    def addIntersections(self, result, schema, s):
        Resource type = s.getSubject()
        List<RDFNode> types = asJavaList(AssemblerHelp.getResource(s))
        Set<Resource> candidates = subjectSet(result, ANY, RDF.type, types.get(0))
        for (int i = 1 i < types.size() i += 1)
            removeElementsWithoutType(candidates, (Resource) types.get(i))
        addTypeToAll(type, candidates)
    

    # param Resource type, Set<Resource> candidates
    # result static void
    def addTypeToAll(self, type, candidates):
        List<Resource> types = equivalentTypes(type)
        for (Resource element : candidates):
            Resource resource = element
            for (Resource type1 : types):
                resource.addProperty(RDF.type, type1)
            
        
    

    # param Resource type
    # result static List<Resource>
    def equivalentTypes(self, type):
        List<Resource> types = ArrayList<>()
        types.add(type)
        for (StmtIterator it = type.getModel().listStatements(ANY, OWL.equivalentClass, type) it.hasNext() )
            types.add(it.nextStatement().getSubject())
        return types
    

    # param Set<Resource> candidates, Resource type
    # result static void
    def removeElementsWithoutType(self, candidates, type):
        for (Iterator<Resource> it = candidates.iterator() it.hasNext() ):
            Resource candidate = it.next()
            if (!candidate.hasProperty(RDF.type, type)) it.remove():
        
    

    # param Model result, Resource S, Property P, RDFNode O
    # result static Set<Resource>
    def subjectSet(self, result, S, P, O):
        return result.listStatements(S, P, O).mapWith(Statement::getSubject).toSet()
    

    # param Resource resource
    # result static List<RDFNode>
    def asJavaList(self, resource):
        return (resource.as(RDFList.class)).asJavaList()
    

    '''
     * A Lisp-style linked list. Used because we want non-updating cons
     * operations.
    '''
    protected static class LinkedSeq:
        final Resource item
        final LinkedSeq rest

        LinkedSeq(Resource item):
            self(item, null)
        

        LinkedSeq(Resource item, LinkedSeq rest):
            self.item = item
            self.rest = rest
        

        LinkedSeq push(Resource item):
            return LinkedSeq(item, self)
        

        # @Override
    # param 
    # result String
    def toString(self):
            StringBuffer result = StringBuffer("[")
            LinkedSeq scan = self
            while (scan is not None):
                result.append(scan.item)
                scan = scan.rest
                result.append(" ")
            
            return result.append("]").toString()
        
    

